﻿using System.Windows;
using System.Windows.Media;
using System.Windows.Data;
using DeepEarth.Map.Core;
using System;
using System.Linq;
using DeepEarth.Map.Core.Utilities;
using DeepEarth.Core.Utilities;
using System.Windows.Controls;
using System.Windows.Markup;
using DeepEarth.Map.Core.Converters;
namespace DeepEarth.Toolkit.Geometry
{
    [TemplatePart(Name = PART_LayoutRoot, Type = typeof(Panel))]
    public class Pushpin : PointBase
    {
        const string PART_LayoutRoot = "PART_LayoutRoot";
        const string PART_Presenter = "PART_Presenter";
        Grid layoutRoot;
        ContentPresenter presenter;

        public static readonly DependencyProperty FillProperty = DependencyProperty.Register("Fill",
            typeof(Brush), typeof(Pushpin), new PropertyMetadata(new SolidColorBrush(Colors.Blue)));

        public Brush Fill
        {
            get { return (Brush)GetValue(FillProperty); }
            set { SetValue(FillProperty, value); }
        }

        public static readonly DependencyProperty BalloonTextProperty = DependencyProperty.Register("BalloonText",
            typeof(string), typeof(Pushpin), new PropertyMetadata(string.Empty));

        public string BalloonText
        {
            get { return (string)GetValue(BalloonTextProperty); }
            set { SetValue(BalloonTextProperty, value); }
        }

        Balloon balloon;

        ScaleTransform scaleTransform;

        // The inverse of the scale transform added to the pushpin
        // Apply to elements that shouldn't resize, i.e. the Balloon
        ScaleTransform inverseScaleTransform;

        public Pushpin()
        {
            this.scaleTransform = new ScaleTransform();
            this.RenderTransform = scaleTransform;

            // Bindings are failing. Setting the RenderTransformOrigin directly

            //Binding transformOriginBinding = new Binding("Layer.PositionOriginProperty") { Source = this, Mode = BindingMode.OneWay, Converter=new PositionOriginToPointConverter() };

            //Binding transformOriginBinding = new Binding();
            //transformOriginBinding.Path = new PropertyPath("Layer.PositionOriginProperty");
            ////transformOriginBinding.Path = new PropertyPath(Layer.PositionOriginProperty);
            //transformOriginBinding.Source = this;
            //transformOriginBinding.Converter = new PositionOriginToPointConverter();
            //this.SetBinding(RenderTransformOriginProperty, transformOriginBinding);

            //string bindingDefinition = "<Binding xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:core=\"clr-namespace:DeepEarth.Map.Core;assembly=DeepEarth.Map.Core\" Path=\"(core:Layer.PositionOrigin)\" />";
            //Binding transformOriginBinding = XamlReader.Load(bindingDefinition) as Binding;
            //transformOriginBinding.Source = this;
            //transformOriginBinding.Converter = new PositionOriginToPointConverter();
            //this.SetBinding(RenderTransformOriginProperty, transformOriginBinding);

            //this.SetValue(Layer.PositionOriginProperty, new PositionOrigin { X = 0.5, Y = 1.0 })

            balloon = new Balloon();
            balloon.Background = new SolidColorBrush(Colors.White);
            balloon.Width = 100;
            balloon.Height = 100;
            inverseScaleTransform = new ScaleTransform();
            balloon.RenderTransform = inverseScaleTransform;
            balloon.Visibility = Visibility.Collapsed;

            Binding ballonTextBinding = new Binding("BalloonText") { Source = this, Mode = BindingMode.OneWay };
            balloon.SetBinding(Balloon.TextProperty, ballonTextBinding);

            this.DefaultStyleKey = typeof(Pushpin);

            // Default Content;
            var actualPushpin = new PushpinIcon();
            this.Content = actualPushpin;

            this.InitializationConditions.Add(() => this.layoutRoot != null);

            this.InitializationActions.Insert(0, () =>
            {
                var fillBinding = new Binding("Fill") { Source = this, Mode = BindingMode.OneWay };
                actualPushpin.SetBinding(PushpinIcon.FillProperty, fillBinding);

                SizeToContent();

                // Attach an event handler to every non balloon child. If the child size changes,
                // recalcuate the pushpin size

                // TODO: Do we just have to find the size of the inner content control?
                Action<FrameworkElement> handleSizeChange = (u) =>
                {
                    u.SizeChanged += (o, e) =>
                    {
                        this.SizeToContent();
                    };

                    Image img = null;

                    if (u is System.Windows.Controls.Image)
                    {
                        img = u as System.Windows.Controls.Image;
                    }
                    else if (u is ContentPresenter)
                    {
                        ContentPresenter c = u as ContentPresenter;

                        if (c.Content is Image)
                        {
                            img = c.Content as Image;
                        }
                    }

                    if (img != null)
                    {
                        if (img.ActualWidth == 0)
                        {
                            img.ImageOpened += (o, e) =>
                            {
                                this.SizeToContent();
                            };
                        }
                    }
                };

                layoutRoot.Children.Where(e => e is UIElement && !(e is Balloon)).Cast<FrameworkElement>().Iter(handleSizeChange);

            }
            );
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.layoutRoot = (Grid)GetTemplateChild(PART_LayoutRoot);
            this.presenter = (ContentPresenter)GetTemplateChild(PART_Presenter);
        }

        // Size to content to ensure that PositionOrigin works correctly
        private void SizeToContent()
        {
            Size sizeMe = CalculateSize(new Size(double.PositiveInfinity, double.PositiveInfinity));

            this.Width = sizeMe.Width;
            this.Height = sizeMe.Height;
        }

        protected Size CalculateSize(Size availableSize)
        {
            Size desiredSize = new Size(0, 0);

            Func<Size, FrameworkElement, Size> maxSizeFunc = (s, u) =>
            {
                u.Measure(availableSize);

                double desiredWidth = Math.Max(u.DesiredSize.Width, u.ActualWidth);
                double desiredHeight = Math.Max(u.DesiredSize.Height, u.ActualHeight);

                if (desiredWidth == double.PositiveInfinity)
                    desiredWidth = s.Width;

                if (desiredHeight == double.PositiveInfinity)
                    desiredHeight = s.Height;

                s.Width = Math.Max(s.Width, desiredWidth);
                s.Height = Math.Max(s.Height, desiredHeight);

                return s;
            };

            // Don't include the balloon in the bounds calculation

            return layoutRoot.Children.Where(e => e is UIElement && !(e is Balloon)).Cast<FrameworkElement>().Fold(maxSizeFunc, desiredSize);
        }


        public override void ApplyScale(double scale, double appliedScaleTransform)
        {
            // Map may be null if the pushpin is currently invisible

            if (Map == null || MaintainConstantSize)
            {
                return;
            }

            double transformScale = scale * Map.AsFrameworkElement.ActualWidth / (Math.Max(17, this.ActualWidth));

            if (Scale != 0)
            {
                transformScale *= Scale;
            }

            //transformScale = Math.Min(transformScale, 1.0);

            double inverseScale = 1 / transformScale;

            if (!double.IsInfinity(inverseScale))
            {
                this.inverseScaleTransform.ScaleX = inverseScale;
                this.inverseScaleTransform.ScaleY = inverseScale;
            }

            this.scaleTransform.ScaleX = transformScale;
            this.scaleTransform.ScaleY = transformScale;
        }

        protected override void SelectedChangedOverride()
        {
            if (IsSelected && !string.IsNullOrEmpty(BalloonText))
            {
                this.balloon.Visibility = Visibility.Visible;

                this.balloon.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
                Size balloonSize = this.balloon.DesiredSize;

                double mapWidth = Map.AsFrameworkElement.ActualWidth;
                double mapHeight = Map.AsFrameworkElement.ActualWidth;

                Point pixelPosition = Map.LocationToViewportPoint(Position);

                if (pixelPosition.Y > mapHeight / 2)
                {
                    balloon.TranslateY = -balloonSize.Height;
                }
                else
                {
                    balloon.TranslateY = this.ActualHeight;
                }

                if (pixelPosition.X > mapWidth / 2)
                {
                    balloon.TranslateX = -balloonSize.Width;
                }
                else
                {
                    balloon.TranslateX = this.ActualWidth;
                }
            }
        }
    }
}
